package RushHour;
import java.io.*;
import java.util.*;

/**
 * This is the class for representing a particular rush hour puzzle.
 * Methods are provided for accessing information about a puzzle, and
 * also for reading in a list of puzzles from a data file.  In
 * addition, this class maintains a counter of the number of search nodes
 * that have been expanded for this puzzle.  Methods for accessing,
 * incrementing or resetting this counter are also provided.
 * <p>
 * Every car is constrained to only move horizontally or vertically.
 * Therefore, each car has one dimension along which it is fixed, and
 * another dimension along which it can be moved.  The fixed dimension
 * is stored here as part of the puzzle.  Also stored here are the
 * sizes and orientations of the cars, the size of the puzzle grid,
 * the name of the puzzle and the initial (root) search node of the
 * puzzle.
 * <p>
 * The goal car is always assigned index 0.
 */
public class Puzzle {

    private String name;
    private Node initNode;

    private int searchCount;

    private int numCars;
    private int fixedPos[];
    private int carSize[];
    private boolean carOrient[];  // 0=horiz. 1=vert.
    private char carChar[];

    private int gridSize;

    /** Returns the number of cars for this puzzle. */
    public int getNumCars() {
	return numCars;
    }

    /** Returns the fixed position of car <tt>v</tt>. */
    public int getFixedPosition(int v) {
	return fixedPos[v];
    }

    /** Returns the size (length) of car <tt>v</tt>. */
    public int getCarSize(int v) {
	return carSize[v];
    }

    /**
     * Returns the orientation of car <tt>v</tt>, where <tt>true</tt>
     * means that the car is vertically oriented.
     */
    public boolean getCarOrient(int v) {
	return carOrient[v];
    }

    /** Increments the search counter by <tt>d</tt>. */
    public void incrementSearchCount(int d) {
	searchCount += d;
    }

    /**
     * Returns the current value of the search counter, which keeps a
     * count of the number of nodes generated on the current
     * search.
     */
    public int getSearchCount() {
	return searchCount;
    }

    /** Resets the search counter to 1 (for the initial node). */
    public void resetSearchCount() {
	searchCount = 1;
    }

    /** Returns the name of this puzzle. */
    public String getName() {
	return name;
    }

    /**
     * Returns the grid size of this puzzle, i.e., the length along
     * each side.
     */
    public int getGridSize() {
	return gridSize;
    }

    /** Returns the initial (root) node of this puzzle. */
    public Node getInitNode() {
	return initNode;
    }
    
    
    /**
     * Constructs the board from standard string representation.
     * 
     * For example, the string "AA...OP..Q.OPXXQ.OP..Q..B...CCB.RRR." encodes
     * a board with 1 red car (X), 2 horizontal cars (A,C), 1 vertical car (B),
     * 1 horizontal truck (R) and 3 vertical trucks (O,P,Q):
     * 
     *      AA...O
     *      P..Q.O
     *      PXXQ.O
     *      P..Q..
     *      B...CC
     *      B.RRR.
     * 
     * @param boardStr the encoded board string.
     */
    public  Puzzle(String boardStr, String name)
    {
        assert(boardStr.length() == RHConstants.NumPositions);
        
        int gridSize;
		int numCars;
		boolean orient[];
		int size[];
		int x[];
		int y[];
		
        int numVehicles = 0;
        Set<Character> foundNames = new HashSet<Character>();
        for (int i = 0; i < boardStr.length(); ++i) {
            char c = boardStr.charAt(i);
            if ((c == RHConstants.EmptyChar) || (foundNames.contains(c))) {
                continue;
            }
            foundNames.add(c);
            ++numVehicles;
        }

        numCars = numVehicles;
        gridSize = RHConstants.BoardSize;
        
        orient = new boolean[numCars];
    	size = new int[numCars];
    	x = new int[numCars];
    	y = new int[numCars];
    	carChar = new char[numCars];
        
        int iVehicleIterator = 1;
        foundNames.clear();
        for (int i = 0; i < boardStr.length(); ++i)
        {
            char c = boardStr.charAt(i);
            
            if ((c == RHConstants.EmptyChar) || (foundNames.contains(c))) {
                continue;
            }
            foundNames.add(c);
            
            if (c == RHConstants.RedCarChar) {
            	this.carChar[0] = c;  //red car is the 1st in the array.
            	orient[0] = false; 
            	size[0] = 2; //correct? 
            	y[0] = i/gridSize; //row #
            	x[0] = i%gridSize; //col #
            } 
            else
            {
            	this.carChar[iVehicleIterator] = c;
            	y[iVehicleIterator] = i/gridSize; //row #
            	x[iVehicleIterator] = i%gridSize; //col #
            	
            	 if (boardStr.charAt(i+1) == c) {
                     if ((i + 2 < RHConstants.NumPositions) &&
                         (boardStr.charAt(i+2) == c)) {
                        //horiz truck 
                     	orient[iVehicleIterator] = false; // false = horiz.
                     	size[iVehicleIterator] = 3; //h.truck
                     	
                     } else { //horiz car
                    	orient[iVehicleIterator] = false; // false = horiz.
                      	size[iVehicleIterator] = 2; //h.car
                         ;
                     }
                 } else if ((i + 2*RHConstants.BoardSize < RHConstants.NumPositions)
                         && (boardStr.charAt(i + 2*RHConstants.BoardSize) == c)) {
                	//vertical truck 
                  	orient[iVehicleIterator] = true; 
                  	size[iVehicleIterator] = 3; 
                 } else {
                	//vertical car
                 	orient[iVehicleIterator] = true;
                   	size[iVehicleIterator] = 2; 
                 }
            	 
            	 iVehicleIterator++;
            }
           
        }
        initNewPuzzle(name, gridSize, numCars, orient, size, carChar, x, y);
    }
    

    /**
     * The main constructor for constructing a puzzle.  You probably
     * will never need to use this constructor directly, since
     * ordinarily puzzles will be constructed by reading them in from
     * a datafile using the <tt>readPuzzlesFromFile</tt> method.  It
     * is assumed that the goal car is always assigned index 0.
     *
     * @param name     the name of the puzzle
     * @param gridSize the size of one side of the puzzle grid
     * @param numCars  the number of cars on this puzzle
     * @param orient   the orientations of each car (<tt>true</tt> = vertical)
     * @param size     the sizes of each car
     * @param x        the x-coordinates of each car
     * @param y        the y-coordinates of each car
     */
    public Puzzle(String name,
		  int gridSize,
		  int numCars,
		  boolean orient[],
		  int size[],
		  char carName[],
		  int x[],
		  int y[]) {
	initNewPuzzle(name, gridSize, numCars, orient, size, carName, x, y);
    }

	private void initNewPuzzle(String sName, int giGidSize, int iNumCars, boolean[] _orient, int[] _size, char[] _carName, int[] _x, int[] _y) {
		this.name = sName;
		this.numCars = iNumCars;
		this.gridSize = giGidSize;
		if (iNumCars <= 0) {
		    throw new IllegalArgumentException("Each puzzle must have a positive number of cars");
		}
		carOrient = new boolean[iNumCars];
		carSize = new int[iNumCars];
		fixedPos = new int[iNumCars];
		int varPos[] = new int[iNumCars];

		boolean grid[][] = new boolean[giGidSize][giGidSize];

		for (int v = 0; v < iNumCars; v++) {
		    carOrient[v] = _orient[v];
		    carSize[v] = _size[v];
		    //this.carName[v] = _carName[v];
		    if (_size[v] <= 0)
		    	throw new IllegalArgumentException("Cars must have positive size");

		    if (_x[v] < 0 || _y[v] < 0
			|| (_orient[v] && _y[v] + _size[v] > giGidSize)
			|| (!_orient[v] && _x[v] + _size[v] > giGidSize))
		    	throw new IllegalArgumentException("Cars must be within bounds of grid");

		    for (int d = 0; d < _size[v]; d++) {
			int xv = _x[v], yv = _y[v];
			if (_orient[v]) yv += d;
			else           xv += d;
			//if (grid[xv][yv])
			    //throw new IllegalArgumentException("Cars cannot overlap");
			grid[xv][yv] = true;
		    }
			    
		    if (_orient[v]) {
			fixedPos[v] = _x[v];
			varPos[v] = _y[v];
		    } else {
			fixedPos[v] = _y[v];
			varPos[v] = _x[v];
		    }

		}

		initNode = new Node(new State(this, varPos), 0, null);

		resetSearchCount();
	}

    /**
     * A static method for reading in a list of puzzles from the data
     * file called <tt>filename</tt>.  Each puzzle is described in the
     * data file using the format described on the assignment.  The
     * set of puzzles is returned as an array of <tt>Puzzle</tt>'s.
     */
    public static Puzzle[] readPuzzlesFromFile(String filename)
    	throws FileNotFoundException, IOException 
    {
    	boolean hasFailed = false;
    	List<Puzzle> result = new LinkedList<Puzzle>();
    	BufferedReader br = null;
        try 
        {
            br = new BufferedReader(new FileReader(filename));
        	int puzzleNum = 1;
        	String boardStr;
            while ((boardStr = br.readLine()) != null)
            {
            	result.add(new Puzzle(boardStr, String.valueOf(puzzleNum++)));
            }
        }
        catch(Exception e)
        {
        	System.out.print("\nError opening file: ");
        	System.out.println(e.getMessage());
        	hasFailed = true;
        }
        finally
        {
        	if (br != null)
        		br.close();
        }
        if (!hasFailed)
        	return result.toArray(new Puzzle[1]);
        else
        	return null;
    }

	public char getCarName(int v) {
		return carChar[v];
	}
	
}
